/* eslint-disable no-useless-escape */
import { isToString, base64Decode, base64Encode } from './encoder'
import {md5} from './md5'

/**
 * 测试
import dataCode from "@/utils/encrypt/dataCode"
const strRaw = "123中国+=□※∷▪☀☼"
const noKey = ""
const key = "Abc"
console.log("原始数据",strRaw)
const noKeyEncode = dataCode.encode(strRaw,noKey)
console.log("Nokey加密",noKeyEncode)
console.log("Nokey解密",dataCode.decode(noKeyEncode,noKey))
const useKeyEncode = dataCode.encode(strRaw,key)
console.log("key加密",useKeyEncode)
console.log("key解密",dataCode.decode(useKeyEncode,key))

const useKeyEncode1S = dataCode.encode(strRaw,key,1)
console.log("key加密(1S)",useKeyEncode1S)
console.log("key解密(1S)",dataCode.decode(useKeyEncode1S,key))
setTimeout(() => {
    console.log("key解密(1S后过期)",dataCode.decode(useKeyEncode1S,key))
},1500)
 */


//Polyfill String.codePointAt
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt
/*! http://mths.be/codepointat v0.1.0 by @mathias */
if (!String.prototype.codePointAt) {
  ;(function () {
    'use strict' // 严格模式，needed to support `apply`/`call` with `undefined`/`null`
    var codePointAt = function (position) {
      if (this == null) {
        throw TypeError()
      }
      var string = String(this)
      var size = string.length
      // 变成整数
      var index = position ? Number(position) : 0
      if (index != index) {
        // better `isNaN`
        index = 0
      }
      // 边界
      if (index < 0 || index >= size) {
        return undefined
      }
      // 第一个编码单元
      var first = string.charCodeAt(index)
      var second
      if (
        // 检查是否开始 surrogate pair
        first >= 0xd800 &&
        first <= 0xdbff && // high surrogate
        size > index + 1 // 下一个编码单元
      ) {
        second = string.charCodeAt(index + 1)
        if (second >= 0xdc00 && second <= 0xdfff) {
          // low surrogate
          // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          return (first - 0xd800) * 0x400 + second - 0xdc00 + 0x10000
        }
      }
      return first
    }
    if (Object.defineProperty) {
      Object.defineProperty(String.prototype, 'codePointAt', {
        value: codePointAt,
        configurable: true,
        writable: true
      })
    } else {
      String.prototype.codePointAt = codePointAt
    }
  })()
}
//Polyfill String.fromCodePoint
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint
if (!String.fromCodePoint) {
  ;(function (stringFromCharCode) {
    var fromCodePoint = function () {
      var codeUnits = [],
        codeLen = 0,
        result = ''
      for (var index = 0, len = arguments.length; index !== len; ++index) {
        var codePoint = +arguments[index]
        // correctly handles all cases including `NaN`, `-Infinity`, `+Infinity`
        // The surrounding `!(...)` is required to correctly handle `NaN` cases
        // The (codePoint>>>0) === codePoint clause handles decimals and negatives
        if (!(codePoint < 0x10ffff && codePoint >>> 0 === codePoint))
          throw RangeError('Invalid code point: ' + codePoint)
        if (codePoint <= 0xffff) {
          // BMP code point
          codeLen = codeUnits.push(codePoint)
        } else {
          // Astral code point; split in surrogate halves
          // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
          codePoint -= 0x10000
          codeLen = codeUnits.push(
            (codePoint >> 10) + 0xd800, // highSurrogate
            (codePoint % 0x400) + 0xdc00 // lowSurrogate
          )
        }
        if (codeLen >= 0x3fff) {
          result += stringFromCharCode.apply(null, codeUnits)
          codeUnits.length = 0
        }
      }
      return result + stringFromCharCode.apply(null, codeUnits)
    }
    try {
      // IE 8 only supports `Object.defineProperty` on DOM elements
      Object.defineProperty(String, 'fromCodePoint', {
        value: fromCodePoint,
        configurable: true,
        writable: true
      })
    } catch (e) {
      String.fromCodePoint = fromCodePoint
    }
  })(String.fromCharCode)
}
/**
 * DZ经典加解密函数Go版本(支持UTF-8字符，支持多字节汉字或特殊符号)
 * 使用Unicode(10进制) 处理待加密原始字符串，前后端多语言统一支持，方便数据交换
 * -----------------------------------------------------------
 * @param bool action true=>ENCODEE / false=>DECODE  加密或解密动作
 * @param string str 加密内容(支持UTF-8字符，支持多字节汉字或特殊符号)
 * @param string key 密钥，可以为空
 * @param int expiry [可选]有效时间秒（秒）
 * @returns string
 */
function _dataCode(isEncode, str, key, expiry) {
  isEncode = !!isEncode
  str = typeof str == 'string' ? str.trim() : ''
  key = typeof key == 'string' ? key.trim() : ''
  expiry = typeof expiry == 'number' ? parseInt(expiry || 0) : 0
  if (str.length < 1) {
    return ''
  }
  function unixTime() {
    return Math.floor(new Date().getTime() / 1000)
  }
  //获取指定Unicode的字符
  function _unicodeToChar(code) {
    return String.fromCodePoint(code)
  }
  //获取指定字符的Unicode (10进制)
  function _charToUnicode(char) {
    return char.codePointAt(0)
  }
  // 前后端统一特殊符号(+/=)便于网络传输，处理Base64，以方便参数传输
  // _base64Decode
  function _base64Decode(str) {
    // 前后端统一，处理Base64，以方便参数传输
    // PHP $str = str_replace(array('-', '_'), array('+', '/'), $str);
    // GO  str = strings.ReplaceAll(str, "-", "+")
    // GO  str = strings.ReplaceAll(str, "_", "/")
    str = str.replace(/\-/g, '+')
    str = str.replace(/\_/g, '/')
    var x = (str.length * 3) % 4
    if (x == 2) {
      str += '=='
    } else if (x == 1) {
      str += '=='
    }
    return base64Decode(str)
  }
  function _base64Encode(str) {
    // 前后端统一处理Base64，移除=以方便参数传输
    // PHP $str = str_replace(array('+', '/', '='), array('-', '_', ''), $data)
    // GO str = strings.ReplaceAll(str, "+", "-")
    // GO str = strings.ReplaceAll(str, "/", "_")
    // GO str = strings.ReplaceAll(str, "=", "")
    str = base64Encode(str)
    str = str.replace(/\+/g, '-')
    str = str.replace(/\//g, '_')
    str = str.replace(/=/g, '')
    return str
  }
  if (typeof str != 'string') {
    return ''
  }
  var ckey_length = 4
  key = md5(key)
  // 密匙a会参与加解密
  var keya = md5(key.substring(0, 16))
  // 密匙b会用来做数据完整性验证
  var keyb = md5(key.substring(16, 16 + 16))
  // 密匙c用于变化生成的密文
  var keyc = ''
  if (ckey_length) {
    if (!isEncode) {
      keyc = str.substring(0, ckey_length)
    } else {
      var md5_time = md5(new Date().toISOString())
      var start = md5_time.length - ckey_length
      keyc = md5_time.substring(start, start + ckey_length)
    }
  }
  // 参与运算的密匙
  var cryptkey = keya + md5(keya + keyc)
  var strbuf
  var i = 0
  if (!isEncode) {
    str = str.substring(ckey_length, str.length)
    strbuf = _base64Decode(str)
  } else {
    expiry = expiry > 0 ? expiry + unixTime() : 0
    var tmpStr = expiry.toString()
    if (tmpStr.length >= 10) {
      str = tmpStr.substring(0, 10) + md5(str + keyb).substring(0, 16) + str
    } else {
      var count = 10 - tmpStr.length
      for (i = 0; i < count; i++) {
        tmpStr = '0' + tmpStr
      }
      str = tmpStr + md5(str + keyb).substring(0, 16) + str
    }
    strbuf = str
  }

  var box = new Array(256)
  for (i = 0; i < 256; i++) {
    box[i] = i
  }
  var rndkey = new Array()
  // 产生密匙簿
  for (i = 0; i < 256; i++) {
    rndkey[i] = _charToUnicode(cryptkey[i % cryptkey.length])
  }
  // 用固定的算法，打乱密匙簿，增加随机性，好像很复杂，实际上对并不会增加密文的强度
  var tmpCode = 0
  for (var j = (i = 0); i < 256; i++) {
    j = (j + box[i] + rndkey[i]) % 256
    tmpCode = box[i]
    box[i] = box[j]
    box[j] = tmpCode
  }

  // 核心加解密部分
  var s = ''
  var dataArray = Array.from(strbuf)
  //获取字符的Unicode后进行位移加密，加密后返回错位的乱码字符串
  for (var a = (j = i = 0); i < dataArray.length; i++) {
    a = (a + 1) % 256
    j = (j + box[a]) % 256
    tmpCode = box[a]
    box[a] = box[j]
    box[j] = tmpCode
    // 从密匙簿得出密匙进行异或，再转成字符
    s += _unicodeToChar(_charToUnicode(dataArray[i]) ^ box[(box[a] + box[j]) % 256])
    //console.log(i,dataArray[i],_charToUnicode(dataArray[i]) ^ box[(box[a] + box[j]) % 256])
  }

  if (isEncode === false) {
    //DECODE
    if (
      (s.substring(0, 10) == 0 || s.substring(0, 10) - unixTime() > 0) &&
      s.substring(10, 10 + 16) == md5(s.substring(26) + keyb).substring(0, 16)
    ) {
      s = s.substring(26)
    } else {
      s = ''
    }
  } else {
    //ENCODE
    s = _base64Encode(s)
    s = keyc + s
  }
  return s
}

export default {
  /**
   * 对字符串进行编码。
   * @param {string} str 需要编码的字符串内容(支持UTF-8字符，数字，多字节汉字或特殊符号)。
   * @param {string} key 编码使用的密钥。
   * @param {number} expiry [可选]有效时间秒（秒）。
   * @returns {string} 编码后的字符串。如果输入不是字符串或为空，则返回空字符串。
   */
  encode: function (str, key, expiry) {
    // 验证str是否为字符串，并确保其不为空
    if ((str = isToString(str)) === false || !str) return ''
    return _dataCode(true, str, key, expiry)
  },
  /**
   * 解码给定的字符串
   * @param {string} str 需要解码的字符串
   * @param {string} key 解码使用的密钥
   * @returns {string} 解码后的字符串。如果输入不是字符串或为空，则返回空字符串。
   */
  decode: function (str, key) {
    // 验证str是否为字符串，并确保其不为空
    if ((str = isToString(str)) === false || !str) return ''
    return _dataCode(false, str, key)
  }
}
